package chapter2_nio.str;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.*;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

public class Server {
    public static void main(String[] args) throws IOException {
        Server server=new Server(9998);
        server.service();
    }
    private Selector selector;
    //创建ServerSocketChannel对象，并将其注册到selector选择器上
    public Server(int port) throws IOException {//√ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √
        //用Selector.open()可以获得一个新的选择器实例(我的理解是和Server端的selector是同一个对象)
        selector=Selector.open();
        ServerSocketChannel ssChannel=ServerSocketChannel.open();//获取ServerSocketChannel实例
        ssChannel.configureBlocking(false);//将ServerSocketChannel配置为非阻塞模式
        ssChannel.bind(new InetSocketAddress("localhost",port));
        ssChannel.register(selector, SelectionKey.OP_ACCEPT);
        //将ssChannel注册到selector选择器上，注册的同时指定此ssChannel的作用为"接受连接"的作用。SelectionKey.OP_ACCEPT（接受连接操作）
        // 这样，当有连接请求到达时，选择器会通知该通道可以进行接受此连接
        System.out.println("服务端开启端口：" + port +"等待客户端链接....");
    }

    void closeChannel(SelectionKey key)throws Exception{
        key.channel().close();
        key.cancel();
        System.out.println("会话结束，服务端关闭连接");
    }

    /**
     * 从selector中获取所有的selectionKey判断判断它是否是可连接的如果是,
     *    就接收客户端连接得到一个SocketChannel并将其注册到selector中。
     * 如果不是可连接的就调用session()方法进行数据传输。
     */
    public void service(){//√ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √
        while(true){
            try{
                //选择器的select()用于等待并选择一个可用的通道，以便进行读取或写入或其他操作(比如连接操作)。
                // 它会阻塞当前线程，直到有任何一个通道变为可读、可写或连接就绪或发生其他感兴趣的事件时sector选择器就会得到通知，
                // 此方法也会向下继续运行。
                // 在返回时，会选择一个或多个通道，并将它们的状态设置为已选择，以便应用程序可以进行相应的操作。
                selector.select();//会一直等待..........(有attachement对象准备好接收数据或准备好发送数据，此select方法都会有返回值)
                //获取selector中触发了对应事件的selectionKey组成的集合，并处理集合中每个selectionKey对应的SocketChannel的事件，
                // 处理完一个就将其对应的selectionKey从Set集合中移除(否则下一次还会将这个事件的SelectionKey再处理一遍)
                Set<SelectionKey> selectionKeySet=selector.selectedKeys();
                System.out.println("get--selectionKeySet:"+selectionKeySet.size());
                Iterator<SelectionKey> iterator=selectionKeySet.iterator();
                while(iterator.hasNext()){
                    SelectionKey key=iterator.next();
                    if(key.isAcceptable()){//判断这个key的状态是否可接受连接
                        ServerSocketChannel ssChannel=(ServerSocketChannel)key.channel();
                        //1.接受连接
                        //从ServerSocketChannel得到连接并返回一个可用的SocketChannel
                        SocketChannel channel=ssChannel.accept();
                        System.out.println("客户端链接成功，连接信息：" + channel+"客户端可以和服务端进行数据传输了...");
                        channel.configureBlocking(false);//将SocketChannel配置为非阻塞模式
                        registerOnConnected(channel,key.selector());//将获得的SocketChannel注册到selector选择器上
                    }else{//如果这个key不能接受连接，就进行数据传输
                        session(key);
                    }
                    //获取selector中的所有selectionKey组成的集合，并处理集合中每个selectionKey对应的SocketChannel的事件，
                    // 处理完一个就将其对应的selectionKey从Set集合中移除(选择器上的selectionKey还在， 没有被删除<只是删除了Set集合中的元素>)
                    System.out.println("before--remove:"+selectionKeySet.size());
                    iterator.remove();
                    System.out.println("remove:"+selectionKeySet.size());
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

    //√ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √
    public void registerOnConnected(SocketChannel channel, Selector selector) throws ClosedChannelException {
        System.out.println("模拟将SocketChannel注册到Selector上,attachment是ReceiverUtil");
        //2.注册一个ReceiverUtil对象到selector上(当下一次用selectedKeys获取触发事件的selectionKey时，
        // 获得此SocketChannel对应的SelectionKey的注册信息<selectionKey对应的attachment对象是ReceiverUtil>)

        channel.register(selector,SelectionKey.OP_READ, new ReceiverUtil(0, String.class));
        //                选择器， 设置SocketChannel可以处理读操作， 采用ReveiverUtil类来处理数据(这个参数叫attachment对象)
        /*Set<SelectionKey> set=selector.selectedKeys();
        for (SelectionKey key: set){
            System.out.println(key+"-"+111+"stepIn"+key.attachment());
            if(key.attachment() instanceof  ReceiverUtil){
                System.out.println( "stepIndex:"+ ((ReceiverUtil)key.attachment()).stepIndex  );
            }
        }*/
    }




    //√ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √
    public void session(SelectionKey key) throws Exception {
        SocketChannel channel=(SocketChannel) key.channel();
        if(key.isReadable()){//判断这个key的状态是否可读
            //获取registerOnConnected()给channel绑定的attachment对象ReceiverUtil(其对应的stepIndex为0)
            ReceiverUtil receiver=(ReceiverUtil) key.attachment();
            //注意：recvFully方法会无限循环调用(在Server类的service()方法中循环调用session方法(session方法又调用recvFully方法))
            //(还有Client类的communicate()方法中循环调用session方法(session方法又调用recvFully方法))
            //所以可能会多次执行recvFully()读取数据长度到ReceiverUtil的countBuffer，多次执行recvFully()读取对象数据到buffer
            //通常是执行一次recvFully()读取数据长度到ReceiverUtil的countBuffer，再执行第二次recvFully()读取对象数据到buffer
            //直到数据读完，这个方法返回true，就执行下边if分支，如果第一次接收数据，就给SelectionKey绑定一个
            // attachment对象SenderUtil(其对应的stepIndex为1)用于返回"I`m fine thank you!"
            if(receiver.recvFully(channel)){//用ReceiverUtil的recvFully()读取channel中的数据
                String s=(String)receiver.getObject();
                System.out.println("客户端发送的数据：" + s);//3.接收How are you
                //如果recvFully()返回true(第一条数据接收完毕，且StepIndex为0<表示第一条数据刚刚发送完>，就创建一个用于发送数据的SenderUtil对象)
                if(receiver.getStepIndex()==0){
                    SenderUtil sender=new SenderUtil(receiver.getStepIndex()+1,"I`m fine thank you!");
                    System.out.println("StepIndex::"+sender.getStepIndex());
                    channel.register(key.selector(), SelectionKey.OP_WRITE, sender);
                    //4.若stepIndex为0，就重新将channel以写模式OP_WRITE， 注册到selector上，创建一个SenderUtil作为attachment设置给SelectionKey
                    //此时channel的attachment已经是SenderUtil了(而不再是ReceiverUtil了)
                    // (设置它的stepIndex为0+1 让它用用于返回"I`m fine thank you")
                }
                if(receiver.getStepIndex()==3){
                    //如果receiver的stepIndex为3(表示服务端的操作都已经进行完毕)，就关闭channel.
                    //刚开始，接收完毕how are you后，会创建一个SenderUtil对象并将其的stepIndex的值设置为receiver的StepIndex+1(为1)
                    //用于发送，i`m fine thank you。  当发送完i`m fine thank you。之后再将SenderUtil对象的StepIndex再+1(为2)，表示即将发送 and you
                    //当发送完and you时候再创建一个ReceiverUtil作为SelectionKey的attachment，并绑定SocketChannel到selector(替换SenderUtil对象)
                    // (并设置stepIndex=sender.StepIndex2+1(用于表示服务端进行第三次数据操作<接收数据<收im fine too>>)。当接收数据的操作执行完毕后。
                    // 判断stepIndex()==3是否满足(满足证明服务端的四个操作都完毕，接收完毕(receiver.stepIndex=0)，发送两条数据完毕(sender.stepIndex=1)(sender.stepIndex=2),
                    // 然后创建receiver对象并设置receiver.stepIndex=3，接收and you也完毕，receiver.stepIndex==3满足(Server端四次通信都完毕)就关闭客户端对应的socketChannel
                    //则立即关闭此时操作的客户端的SocketChannel并从选择器中移除对应的selectionKey。
                    closeChannel(key);
                }
            }

        }else{//判断这个key的状态是否可写(key的状态不属于可读 就是可写
             // <因为服务端接收某客户端连接后立即将SocketChannel重新注册到selector上为可读状态<没有OP_ACCEPT状态了>>)
            //注意：上边的if先执行了，将SenderUtil的stepIndex设置为1了
            SenderUtil sender=(SenderUtil) key.attachment();//获取写状态对应的SenderUtil对象(用于将数据写出到客户端)
            //注意：sendFully()方法会无限循环调用(在Server类的service()方法中循环调用session方法(session方法又调用sendFully方法))
            //(还有Client类的communicate()方法中循环调用session方法(session方法又调用sendFully方法))
            //所以可能会多次执行将countBuffer中的数据写出，然后多次将buffer中的数据("I`m fine thank you!")写出到channel中(返回给客户端)
            //(长度发送完后再多次发送buffer中的对象数据到channel中)
            //5.sendFully()给客户端返回"I`m fine thank you!", 返回完毕后在if分支内，SenderUtil的stepIndex为1(上边创建SenderUtil时设置的)
            if(sender.sendFully(channel)){
                //给客户端返回数据之前先用sender.getObject()获取 SenderUtil中Object obj中存储的数据"I`m fine thank you!"，并打印
                System.out.println("服务端返回：" + sender.getObject());
                //如果sendFully()返回true表示Server端第一次给Client返回数据完成了。
                //且stepIndex为1，就再给SectionKey对应的attachment对应的sender设置的stepIndex+1变为2(表示即将第二次给Client返回数据)
                if(sender.getStepIndex()==1) {//(表明"and you?"是给客户端第二次返回数据)
                    // 6.第二次执行session方法时SelectionKey状态还是OP_WRITE，则再次给客户端返回"and you?",并且重新创建
                    //attachment对象SenderUtil并设置stepIndex的值为2(但是sender还指向原来的SenderUtil对象，所以sender对象的stepIndex的值还是1，
                    //(如果下次重新从key.attachment()获取到sender再获取stepIndex，则获取的就是新的attachment的stepIndex的值2)
                    //receiver的stepIndex的值还是1)
                    key.attach(new SenderUtil(sender.getStepIndex() + 1, "and you?"));
                    System.out.println("key.attachment()).getStepIndex()::"+((SenderUtil)key.attachment()).getStepIndex());
                }
                System.out.println(sender.getStepIndex()+"::sender.getStepIndex()");
                if(sender.getStepIndex()==2){//7.如果服务端的stepIndex为2,就表示服务端正在给客户端返回第二条数据或已经给客户端返回了两次数据了，
                    //就再创建一个ReceiverUtil对象，并设置stepIndex为3(此时sender的stepIndex还是2)
                    //用于接受客户端发送的第二次数据(接收两次数据，就要给selector上的SocketChannel绑定两次ReceiverUtil对象)
                    ReceiverUtil receiver=new ReceiverUtil(sender.getStepIndex()+1, String.class);
                    System.out.println(receiver.getStepIndex()+"and you完毕，准备接收");
                    channel.register(key.selector(), SelectionKey.OP_READ, receiver);
                }
            }
        }

    }




}



//本案例的Server中用到的关键类:
//1.ServerSocketChannel: 类似于之前的ServerSocket，它负责启用一个端口并监听，
//                       当有客户端连接时，可通过accept()方法得到一个SocketChannel实例来与客户端进行数据通信。
//2.SocketChannel: 类似于之前的Socket，它负责与客户端进行数据通信。
//3.Selector: 选择器，ServerSocketChannel需要向Selector选择器注册并分配一个SelectionKey
//            (通过SelectionKey找到ServerSocketChannel或其他SocketChannel)。
//            它负责等待有就绪的通道并选择一个已经就绪的通道，以便进行读取或写入或其他操作(比如连接操作)。



